//------------------------------------------------------------------------------
// <copyright file="CounterSetInstanceCounterDataSet.cs" company="Microsoft">
//     Copyright (c) Microsoft Corporation.  All rights reserved.
// </copyright>
//------------------------------------------------------------------------------

namespace System.Diagnostics.PerformanceData {
    using System;
    using System.Threading;
    using System.Runtime.InteropServices;
    using System.ComponentModel;
    using System.Collections.Generic;
    using System.Diagnostics;
    using System.Diagnostics.CodeAnalysis;
    using System.Security;
    using Microsoft.Win32;

    /// <summary>
    /// CounterData class is used to store actual raw counter data. It is the value element within
    /// CounterSetInstanceCounterDataSet, which is part of CounterSetInstance.
    /// </summary>
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class CounterData {
        [SecurityCritical]
        unsafe private Int64 * m_offset;

        /// <summary>
        /// CounterData constructor
        /// </summary>
        /// <param name="counterId"> counterId would come from CounterSet::AddCounter() parameter </param>
        /// <param name="pCounterData"> The memory location to store raw counter data </param>
        [System.Security.SecurityCritical]
        unsafe internal CounterData(Int64 * pCounterData) {
            m_offset    = pCounterData;
            * m_offset  = 0;
        }

        /// <summary>
        /// Value property it used to query/update actual raw counter data.
        /// </summary>
        public Int64 Value {
            [System.Security.SecurityCritical]
            get {
                unsafe {
                    return Interlocked.Read(ref (* m_offset));
                }
            }
            [System.Security.SecurityCritical]
            set {
                unsafe {
                    Interlocked.Exchange(ref (* m_offset), value);
                }
            }
        }

        [System.Security.SecurityCritical]
        public void Increment() {
            unsafe {
                Interlocked.Increment (ref (*m_offset));
            }   
        }

        [System.Security.SecurityCritical]
        public void Decrement() {
            unsafe {
                Interlocked.Decrement (ref (*m_offset));
            }   
        }

        [System.Security.SecurityCritical]
        public void IncrementBy(Int64 value) {
            unsafe {
                Interlocked.Add (ref (*m_offset), value);
            }   
        }

        /// <summary>
        /// RawValue property it used to query/update actual raw counter data.
        /// This property is not thread-safe and should only be used 
        /// for performance-critical single-threaded access.
        /// </summary>
        public Int64 RawValue {
            [System.Security.SecurityCritical]
            get {
                unsafe {
                    return (* m_offset);
                }
            }
            [System.Security.SecurityCritical]
            set {
                unsafe {
                    * m_offset = value;
                }
            }
        }

        }

    /// <summary>
    /// CounterSetInstanceCounterDataSet is part of CounterSetInstance class, and is used to store raw counter data
    /// for all counters added in CounterSet.
    /// </summary>
    [System.Security.Permissions.HostProtection(MayLeakOnAbort = true)]
    public sealed class CounterSetInstanceCounterDataSet : IDisposable {
        internal        CounterSetInstance             m_instance;
        private         Dictionary<Int32, CounterData> m_counters;
        private         Int32                          m_disposed;
        [SecurityCritical]
        unsafe internal byte *                         m_dataBlock;

        [System.Security.SecurityCritical]
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        internal CounterSetInstanceCounterDataSet(CounterSetInstance thisInst) {
            m_instance = thisInst;
            m_counters = new Dictionary<Int32, CounterData>();

            unsafe {
                if (m_instance.m_counterSet.m_provider == null) {
                    throw new ArgumentException(SR.GetString(SR.Perflib_Argument_ProviderNotFound, m_instance.m_counterSet.m_providerGuid), "ProviderGuid");
                }
                if (m_instance.m_counterSet.m_provider.m_hProvider.IsInvalid) {
                    throw new InvalidOperationException(SR.GetString(SR.Perflib_InvalidOperation_NoActiveProvider, m_instance.m_counterSet.m_providerGuid));
                }

                m_dataBlock = (byte *) Marshal.AllocHGlobal(m_instance.m_counterSet.m_idToCounter.Count * sizeof(Int64));
                if (m_dataBlock == null) {
                    throw new InsufficientMemoryException(SR.GetString(SR.Perflib_InsufficientMemory_InstanceCounterBlock, m_instance.m_counterSet.m_counterSet, m_instance.m_instName));
                }

                Int32 CounterOffset = 0;

                foreach (KeyValuePair<Int32, CounterType> CounterDef in m_instance.m_counterSet.m_idToCounter) {
                    CounterData thisCounterData = new CounterData((Int64 *) (m_dataBlock + CounterOffset * sizeof(Int64)));

                    m_counters.Add(CounterDef.Key, thisCounterData);
                    // ArgumentNullException - CounterName is NULL
                    // ArgumentException - CounterName already exists.

                    uint Status = UnsafeNativeMethods.PerfSetCounterRefValue(
                                    m_instance.m_counterSet.m_provider.m_hProvider,
                                    m_instance.m_nativeInst,
                                    (uint) CounterDef.Key,
                                    (void *) (m_dataBlock + CounterOffset * sizeof(Int64)));
                    if (Status != (uint) UnsafeNativeMethods.ERROR_SUCCESS) {
                        Dispose(true);

                        // ERROR_INVALID_PARAMETER or ERROR_NOT_FOUND
                        switch (Status) {
                            case (uint) UnsafeNativeMethods.ERROR_NOT_FOUND:
                                throw new InvalidOperationException(SR.GetString(SR.Perflib_InvalidOperation_CounterRefValue, m_instance.m_counterSet.m_counterSet, CounterDef.Key, m_instance.m_instName));

                            default:
                                throw new Win32Exception((int) Status);
                        }
                    }
                    CounterOffset ++;
                }
            }
        }

        [System.Security.SecurityCritical]
        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }
        [System.Security.SecurityCritical]
        ~CounterSetInstanceCounterDataSet() {
            Dispose(false);
        }
        [System.Security.SecurityCritical]
        private void Dispose(bool disposing) {
            if (Interlocked.Exchange(ref m_disposed, 1) == 0) {
                unsafe {
                    if (m_dataBlock != null) {
                        // Need to free allocated heap memory that is used to store all raw counter data.
                        Marshal.FreeHGlobal((System.IntPtr)m_dataBlock);
                        m_dataBlock = null;
                    }
                }
            }
        }

        /// <summary>
        /// CounterId indexer to access specific CounterData object.
        /// </summary>
        /// <param name="counterId">CounterId that matches one CounterSet::AddCounter()call</param>
        /// <returns>CounterData object with matched counterId</returns>
        public CounterData this[Int32 counterId] {
            get {
                if (m_disposed != 0) {
                    return null;
                }

                try {
                    return m_counters[counterId];
                }
                catch (KeyNotFoundException) {
                    return null;
                }
                catch {
                    throw;
                }
            }
        }

        /// <summary>
        /// CounterName indexer to access specific CounterData object.
        /// </summary>
        /// <param name="counterName">CounterName that matches one CounterSet::AddCounter() call</param>
        /// <returns>CounterData object with matched counterName</returns>
        [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")]
        public CounterData this[String counterName] {
            get {
                if (counterName == null) {
                    throw new ArgumentNullException("CounterName");
                }
                if (counterName.Length == 0) {
                    throw new ArgumentNullException("CounterName");
                }
                if (m_disposed != 0) {
                    return null;
                }

                try {
                    Int32 CounterId = m_instance.m_counterSet.m_stringToId[counterName];
                    try {
                        return m_counters[CounterId];
                    }
                    catch (KeyNotFoundException) {
                        return null;
                    }
                    catch {
                        throw;
                    }
                }
                catch (KeyNotFoundException) {
                    return null;
                }
                catch {
                    throw;
                }
            }
        }
    }
}
